Isolation Forest × STL + IQR
Análise de Dados Ambientais
Universidade Estadual de Feira de Santana (UEFS)
Objetivo Central
Compreender e aplicar técnicas de detecção de anomalias (outliers) em séries temporais ambientais, comparando abordagens estatísticas clássicas e de aprendizado de máquina.
Um outlier ou anomalia é uma observação que se desvia significativamente do padrão esperado dos dados.
Em dados ambientais, anomalias podem representar:
Por que detectar anomalias?
| Tipo | Descrição | Exemplo ambiental |
|---|---|---|
| Pontual | Um único valor atípico isolado | Pico de chuva de 200 mm em um dia seco |
| Contextual | Valor normal em si, mas atípico no contexto temporal | 30 °C em julho no semiárido (normal), mas 30 °C em janeiro (atípico) |
| Coletiva | Sequência de valores que formam um padrão anômalo | Semana inteira sem chuva na estação chuvosa |
Nesta aula usaremos
Série de precipitação diária de uma estação da ANA no semiárido baiano (2000-2024), disponível gratuitamente no HidroWeb.
Acesso: hidroweb.ana.gov.br
STL - Seasonal and Trend decomposition using Loess
A decomposição STL separa a série temporal em três componentes:
\[Y_t = T_t + S_t + R_t\]
Onde:
A detecção de anomalias foca no resíduo: valores de \(R_t\) fora do intervalo esperado são potenciais outliers.
Vantagens do STL
statsmodels)Sobre os resíduos \(R_t\), aplicamos a regra do intervalo interquartil (IQR):
\[IQR = Q_3 - Q_1\]
Um valor é classificado como anomalia se:
\[R_t < Q_1 - 1{,}5 \times IQR \quad \text{ou} \quad R_t > Q_3 + 1{,}5 \times IQR\]
Interpretação prática: após remover tendência e sazonalidade, os resíduos “puros” que ultrapassam os limites do boxplot são candidatos a anomalia.
import pandas as pd
import numpy as np
from statsmodels.tsa.seasonal import STL
import matplotlib.pyplot as plt
# 1. Carregar dados de chuva diária (exemplo simulado)
np.random.seed(42)
n = 365 * 5 # 5 anos
datas = pd.date_range('2019-01-01', periods=n, freq='D')
sazonal = 10 * np.sin(2 * np.pi * np.arange(n) / 365)
tendencia = np.linspace(5, 7, n)
ruido = np.random.exponential(2, n)
chuva = np.maximum(0, tendencia + sazonal + ruido)
# Inserir anomalias artificiais
chuva[100] = 120 # Evento extremo
chuva[800] = 95 # Evento extremo
chuva[1200] = -5 # Erro de sensor (negativo)
df = pd.DataFrame({'data': datas, 'chuva_mm': chuva}).set_index('data')# 2. Decomposição STL (período = 365 dias)
stl = STL(df['chuva_mm'], period=365, robust=True)
resultado = stl.fit()
# 3. Extrair resíduos e aplicar IQR
residuos = resultado.resid
Q1 = residuos.quantile(0.25)
Q3 = residuos.quantile(0.75)
IQR = Q3 - Q1
limite_inf = Q1 - 1.5 * IQR
limite_sup = Q3 + 1.5 * IQR
# 4. Classificar anomalias
anomalias_stl = df[(residuos < limite_inf) | (residuos > limite_sup)]
print(f"Anomalias detectadas (STL+IQR): {len(anomalias_stl)}")O Isolation Forest (Liu et al., 2008) parte de um princípio simples:
Anomalias são poucas e diferentes - portanto, mais fáceis de “isolar”.
O algoritmo constrói árvores de decisão aleatórias que particionam o espaço dos dados. Pontos anômalos necessitam de menos partições para serem isolados.
O score de anomalia é baseado na profundidade média da árvore:
Parâmetros principais
| Parâmetro | Descrição |
|---|---|
n_estimators |
Nº de árvores (100-300) |
contamination |
Proporção esperada de anomalias (0.01-0.05) |
max_samples |
Tamanho da subamostra |
contamination é o parâmetro mais sensível para dados ambientais.
contaminationfrom sklearn.ensemble import IsolationForest
# Engenharia de atributos para captar padrão temporal
df['dia_ano'] = df.index.dayofyear
df['chuva_media_7d'] = df['chuva_mm'].rolling(7, min_periods=1).mean()
df['chuva_desvio_7d'] = df['chuva_mm'].rolling(7, min_periods=1).std().fillna(0)
# Selecionar features
features = ['chuva_mm', 'dia_ano', 'chuva_media_7d', 'chuva_desvio_7d']
X = df[features].dropna()
# Ajustar Isolation Forest
iso_forest = IsolationForest(
n_estimators=200,
contamination=0.02, # espera-se ~2% de anomalias
random_state=42
)
df.loc[X.index, 'anomalia_if'] = iso_forest.fit_predict(X)
# Filtrar anomalias (label = -1)
anomalias_if = df[df['anomalia_if'] == -1]
print(f"Anomalias detectadas (Isolation Forest): {len(anomalias_if)}")| Critério | STL + IQR | Isolation Forest |
|---|---|---|
| Tipo de abordagem | Estatística / decomposição | Machine Learning |
| Pré-requisito | Série com sazonalidade definida | Dados tabulares (com engenharia de features) |
| Interpretabilidade | Alta - resíduos explicáveis | Baixa - score opaco |
| Detecção contextual | Sim (remove sazonalidade) | Parcial (depende das features) |
| Multivariado | Não (univariado) | Sim |
| Parâmetro crítico | Multiplicador IQR (1,5 ou 3) | contamination |
| Custo computacional | Baixo | Moderado |
fig, axes = plt.subplots(2, 1, figsize=(14, 8), sharex=True)
# STL + IQR
axes[0].plot(df.index, df['chuva_mm'], alpha=0.5, label='Chuva (mm)')
axes[0].scatter(anomalias_stl.index, anomalias_stl['chuva_mm'],
color='red', s=30, zorder=5, label='Anomalia STL+IQR')
axes[0].set_title('Detecção por STL + IQR')
axes[0].legend()
# Isolation Forest
axes[1].plot(df.index, df['chuva_mm'], alpha=0.5, label='Chuva (mm)')
axes[1].scatter(anomalias_if.index, anomalias_if['chuva_mm'],
color='orange', s=30, zorder=5, label='Anomalia IF')
axes[1].set_title('Detecção por Isolation Forest')
axes[1].legend()
plt.tight_layout()
plt.show()Recomendação prática: em projetos ambientais, aplique ambas as técnicas e analise a concordância. Anomalias detectadas por ambos os métodos têm maior confiabilidade.
contamination=0.02 e contamination=0.05Entrega
Obrigado!
Luiz Diego Vidal Santos
Universidade Estadual de Feira de Santana (UEFS)
UEFS | Análise de Dados Ambientais | Detecção de Anomalias